/*
 * Copyright (C) 2010-2012 Geometer Plus <contact@geometerplus.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package cn.dns.zlibrary.core.network;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import cn.dns.zlibrary.core.util.ZLNetworkUtil;

import com.mato.sdk.proxy.Address;
import com.mato.sdk.proxy.Proxy;

public class ZLNetworkManager {
	private static ZLNetworkManager ourManager;

	public static ZLNetworkManager Instance() {
		if (ourManager == null) {
			ourManager = new ZLNetworkManager();
		}
		return ourManager;
	}

	public void perform(ZLNetworkRequest request, Address address)
			throws ZLNetworkException {
		boolean success = false;
		DefaultHttpClient httpClient = null;
		HttpEntity entity = null;
		try {
			final HttpContext httpContext = new BasicHttpContext();

			request.doBefore();
			final HttpParams params = new BasicHttpParams();
			HttpConnectionParams.setSoTimeout(params, 30000);// 读取数据超时时间
			HttpConnectionParams.setConnectionTimeout(params, 30000);// 连接超时时间
			httpClient = new DefaultHttpClient(params);
			if (address != null) {
				final HttpHost proxyHost = new HttpHost(address.getHost(),
						address.getPort());
				httpClient.getParams().setParameter(
						ConnRouteParams.DEFAULT_PROXY, proxyHost);
			}

			final HttpRequestBase httpRequest;

			if (request.PostData != null) {

				httpRequest = new HttpPost(request.URL);
				((HttpPost) httpRequest).setEntity(new StringEntity(
						request.PostData, "utf-8"));

			} else if (!request.PostParameters.isEmpty()) {
				httpRequest = new HttpPost(request.URL);
				final List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>(
						request.PostParameters.size());
				for (Map.Entry<String, String> entry : request.PostParameters
						.entrySet()) {
					list.add(new BasicNameValuePair(entry.getKey(), entry
							.getValue()));
				}
				((HttpPost) httpRequest).setEntity(new UrlEncodedFormEntity(
						list, "utf-8"));
			} else {
				httpRequest = new HttpGet(request.URL);

			}
			httpRequest.setHeader("Accept-Encoding", "gzip");
			httpRequest.setHeader("Accept-Language", Locale.getDefault()
					.getLanguage());
			HttpResponse response = null;
			IOException lastException = null;
			for (int retryCounter = 0; retryCounter < 3 && entity == null; ++retryCounter) {
				try {
					response = httpClient.execute(httpRequest, httpContext);
					entity = response.getEntity();
					lastException = null;
				} catch (IOException e) {
					lastException = e;
				}
			}
			if (lastException != null) {
				throw lastException;
			}
			final int responseCode = response.getStatusLine().getStatusCode();

			InputStream stream = null;
			if (entity != null && responseCode == HttpURLConnection.HTTP_OK) {
				stream = entity.getContent();
			}

			if (stream != null) {
				try {
					final Header encoding = entity.getContentEncoding();
					if (encoding != null
							&& "gzip".equalsIgnoreCase(encoding.getValue())) {
						stream = new GZIPInputStream(stream);
					}
					request.handleStream(stream,
							(int) entity.getContentLength());
				} finally {
					stream.close();
				}
				success = true;
			} else {
				if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
					throw new ZLNetworkException(
							ZLNetworkException.ERROR_AUTHENTICATION_FAILED);
				} else {
					throw new ZLNetworkException(true, response.getStatusLine()
							.toString());
				}
			}
		} catch (ZLNetworkException e) {
			throw e;
		} catch (IOException e) {
			e.printStackTrace();
			final String code;
			if (e instanceof UnknownHostException) {
				code = ZLNetworkException.ERROR_RESOLVE_HOST;
			} else {
				code = ZLNetworkException.ERROR_CONNECT_TO_HOST;
			}
			throw new ZLNetworkException(code,
					ZLNetworkUtil.hostFromUrl(request.URL), e);
		} catch (Exception e) {
			e.printStackTrace();
			throw new ZLNetworkException(true, e.getMessage(), e);
		} finally {
			request.doAfter(success);
			if (httpClient != null) {
				httpClient.getConnectionManager().shutdown();
			}
			if (entity != null) {
				try {
					entity.consumeContent();
				} catch (IOException e) {
				}
			}
		}
	}

	public void perform(List<ZLNetworkRequest> requests, Address address)
			throws ZLNetworkException {
		if (requests.size() == 0) {
			return;
		}
		if (requests.size() == 1) {
			perform(requests.get(0), address);
			return;
		}
		HashSet<String> errors = new HashSet<String>();
		for (ZLNetworkRequest r : requests) {
			try {
				perform(r, address);
			} catch (ZLNetworkException e) {
				e.printStackTrace();
				errors.add(e.getMessage());
			}
		}
		if (errors.size() > 0) {
			StringBuilder message = new StringBuilder();
			for (String e : errors) {
				if (message.length() != 0) {
					message.append(", ");
				}
				message.append(e);
			}
			throw new ZLNetworkException(true, message.toString());
		}
	}

	public final void downloadToFile(String url, final File outFile,
			Address address) throws ZLNetworkException {
		downloadToFile(url, null, outFile, 8192, address);
	}

	public final void downloadToFile(String url, String sslCertificate,
			final File outFile, Address address) throws ZLNetworkException {
		downloadToFile(url, sslCertificate, outFile, 8192, address);
	}

	public final void downloadToFile(String url, String sslCertificate,
			final File outFile, final int bufferSize, Address address)
			throws ZLNetworkException {
		perform(new ZLNetworkRequest(url, sslCertificate, null) {
			public void handleStream(InputStream inputStream, int length)
					throws IOException, ZLNetworkException {
				OutputStream outStream = new FileOutputStream(outFile);
				try {
					final byte[] buffer = new byte[bufferSize];
					while (true) {
						final int size = inputStream.read(buffer);
						if (size <= 0) {
							break;
						}
						outStream.write(buffer, 0, size);
					}
				} finally {
					outStream.close();
				}
			}
		}, address);
	}
}
